infoЗаказчик исследования - Яндекс.Практикум. Выявленная проблема - низкая активность студентов в чатах, отсутствие реакции на важные анонсы. Для анализа предоставлен датасет с историей сообщений за определенный период. Необходимо:
Цель исследования: Выявить особенности активности студентов в чатах и определить, когда лучше публиковать посты, чтобы получить больше отклика.
Ход исследования: Данные представлены в таблице, предоставленной Яндекс.Практикумом. После изучения информации выполним поиск дубликатов и заполнение пропусков, если это возможно и необходимо. Добавим необходимые для анализа столбцы. Выполним расчеты путем создания группировок и покажем результаты на графиках. Сделаем выводы и представим их в конце.
Описание данных: Файл chat_data_clean.csv:
Unnamed: 0 индексclient_msg_id id сообщения type тип поста user id пользователяts дата постаthread_ts дата тредаlatest_reply дата ответаteam факт вхождения в неизвестную группуsubtype метка действий пользователя channel канал file_date дата файла attachments прикреплённые файлы reactions реакции text_len длина текста сообщенияtext_words количество слов в сообщении# импортируем библиотеки
import pandas as pd
import time
import re
import ast
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
from plotly import graph_objects as go
from datetime import timedelta
import warnings
warnings.filterwarnings('ignore')
sns.set_style('whitegrid')
def custom_data_parser(cell):
'дата парсер для загрузчика pandas.read_csv'
try:
return pd.to_datetime(cell, unit='s').round('s') # обрабатываем ts, thread_ts, reply
except:
return pd.to_datetime(cell) # обрабатываем file_date
# загружаем данные
df = pd.read_csv('chat_data_clean.csv', index_col=0,
parse_dates=['ts', 'thread_ts', 'latest_reply', 'file_date'],
date_parser=custom_data_parser)
df.head()
| client_msg_id | type | user | ts | latest_reply | team | thread_ts | subtype | channel | file_date | attachments | reactions | text_len | text_words | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | ae31e785-257b-4290-a4c6-9721337f67ea | message | U03JYMWQLP5 | 2022-11-28 13:49:23 | 2022-11-28 14:24:08 | TPV9DP0N4 | 2022-11-28 13:49:23 | NaN | data-analysts-bus | 2022-11-28 | 0 | NaN | 297 | 47 |
| 1 | 7f644ae8-16d4-4c9e-9c4f-8b2d6df6a28d | message | U03V483FRKM | 2022-11-28 14:24:08 | NaT | TPV9DP0N4 | 2022-11-28 13:49:23 | NaN | data-analysts-bus | 2022-11-28 | 0 | [{'name': 'pray', 'users': ['U03JYMWQLP5'], 'c... | 434 | 63 |
| 2 | NaN | message | U02KVQJHQ5S | 2022-11-28 14:48:50 | NaT | NaN | NaT | channel_join | data-analysts-bus | 2022-11-28 | 0 | NaN | 37 | 5 |
| 3 | 6c5bf2c1-8579-413c-8e8f-ec3a0e3698a8 | message | U03JYMWQLP5 | 2022-11-29 08:08:12 | 2022-11-29 10:56:57 | NaN | 2022-11-29 08:08:12 | NaN | data-analysts-bus | 2022-11-29 | 0 | [{'name': 'cat-high-five', 'users': ['U040E2D6... | 69 | 12 |
| 4 | b5e3413b-8f04-4192-948b-2423eb3192b2 | message | U040E2D6CF2 | 2022-11-29 08:32:32 | NaT | TPV9DP0N4 | 2022-11-29 08:08:12 | NaN | data-analysts-bus | 2022-11-29 | 0 | NaN | 19 | 2 |
# общая информация о датасете
df.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 26533 entries, 0 to 26532 Data columns (total 14 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 client_msg_id 18262 non-null object 1 type 26533 non-null object 2 user 23643 non-null object 3 ts 26533 non-null datetime64[ns] 4 latest_reply 2303 non-null datetime64[ns] 5 team 15857 non-null object 6 thread_ts 18222 non-null datetime64[ns] 7 subtype 8317 non-null object 8 channel 26533 non-null object 9 file_date 26533 non-null datetime64[ns] 10 attachments 26533 non-null int64 11 reactions 4169 non-null object 12 text_len 26533 non-null int64 13 text_words 26533 non-null int64 dtypes: datetime64[ns](4), int64(3), object(7) memory usage: 3.0+ MB
Датасет содержит информацию о 26533 сообщениях пользователей. Необходимо проанализировать пропуски и дубликаты при наличии.
# проверим данные в столбце type на дубликаты
df['type'].value_counts()
message 26533 Name: type, dtype: int64
# удаляем не несущий информации столбец 'type' - все сообщения имеют тип 'message'
df = df.drop('type', axis=1)
# проверим количество пропусков
pd.DataFrame(round(df.isna().mean()*100,2)).style.background_gradient('coolwarm')
| 0 | |
|---|---|
| client_msg_id | 31.170000 |
| user | 10.890000 |
| ts | 0.000000 |
| latest_reply | 91.320000 |
| team | 40.240000 |
| thread_ts | 31.320000 |
| subtype | 68.650000 |
| channel | 0.000000 |
| file_date | 0.000000 |
| attachments | 0.000000 |
| reactions | 84.290000 |
| text_len | 0.000000 |
| text_words | 0.000000 |
Пропуски в столбце reactions - это отсутствие реакций, пропуски оставим как есть. Пропуски в latest_reply и thread_ts также означают отсутствие ответов и создания треда соответственно, оставим их как есть. Пропуски в столбце team оставим без изменений, так как нет данных, что обозначает этот столбец.
# проверим какие значения присутствуют в столбце 'subtype'
df['subtype'].value_counts()
channel_leave 3265 bot_message 2890 channel_join 2026 thread_broadcast 108 tombstone 14 channel_unarchive 10 channel_purpose 2 bot_remove 1 channel_name 1 Name: subtype, dtype: int64
Значения столбца subtype - это служебные сообщения и отметки. Заполнять пропуски нет необходимости, нужно считать что сообщения без этих отметок, скорее всего, в большинстве своем от реальных людей.
# проверим зависимость отсутствия id сообщения и служебных меток
df1 = df[df['client_msg_id'].isnull()].groupby('subtype')[['ts']].count()
df1
| ts | |
|---|---|
| subtype | |
| bot_message | 2890 |
| bot_remove | 1 |
| channel_join | 2026 |
| channel_leave | 3265 |
| channel_name | 1 |
| channel_purpose | 2 |
| channel_unarchive | 10 |
| tombstone | 14 |
Большинство пропущенных значений client_id_msg - это сообщения со служебными метками.
# проверим сколько сообщений не имеют id и не имеют служебных меток
df[(df['client_msg_id'].isnull())&(df['subtype'].isnull())]
| client_msg_id | user | ts | latest_reply | team | thread_ts | subtype | channel | file_date | attachments | reactions | text_len | text_words | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 197 | NaN | U01QMDLG9AP | 2022-12-13 14:06:04 | 2022-12-13 14:19:01 | TPV9DP0N4 | 2022-12-13 14:06:04 | NaN | data_edteam_info | 2022-12-13 | 1 | NaN | 208 | 30 |
| 2064 | NaN | U0203TTA1AQ | 2022-11-27 16:19:18 | 2022-12-11 08:57:06 | TPV9DP0N4 | 2022-11-27 16:19:18 | NaN | da_55_projects | 2022-11-27 | 0 | NaN | 227 | 36 |
| 2065 | NaN | U0203TTA1AQ | 2022-11-27 16:19:18 | NaT | TPV9DP0N4 | NaT | NaN | da_55_projects | 2022-11-27 | 0 | NaN | 182 | 30 |
| 2066 | NaN | U0203TTA1AQ | 2022-11-27 16:19:19 | 2022-12-07 20:01:34 | TPV9DP0N4 | 2022-11-27 16:19:19 | NaN | da_55_projects | 2022-11-27 | 0 | NaN | 522 | 82 |
| 2067 | NaN | U0203TTA1AQ | 2022-11-27 16:19:19 | 2022-12-05 15:29:32 | TPV9DP0N4 | 2022-11-27 16:19:19 | NaN | da_55_projects | 2022-11-27 | 0 | NaN | 207 | 33 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 18174 | NaN | U0203TTA1AQ | 2022-11-27 15:58:50 | NaT | TPV9DP0N4 | NaT | NaN | ds_49_projects | 2022-11-27 | 0 | NaN | 255 | 32 |
| 18175 | NaN | U0203TTA1AQ | 2022-11-27 15:58:50 | 2022-12-10 20:28:09 | TPV9DP0N4 | 2022-11-27 15:58:50 | NaN | ds_49_projects | 2022-11-27 | 0 | NaN | 66 | 9 |
| 23639 | NaN | U04ABUPLS83 | 2022-12-12 09:31:44 | NaT | TPV9DP0N4 | NaT | NaN | ds_58_teamwork | 2022-12-12 | 1 | NaN | 14 | 1 |
| 25971 | NaN | U0431FHJFV5 | 2022-12-01 14:33:18 | NaT | TPV9DP0N4 | NaT | NaN | ds_plus_18_teamwork | 2022-12-01 | 1 | NaN | 0 | 0 |
| 26452 | NaN | U02JSUUMARF | 2022-12-14 11:17:54 | NaT | TPV9DP0N4 | NaT | NaN | sql_info | 2022-12-14 | 1 | NaN | 281 | 34 |
62 rows × 13 columns
Сообщений без служебных меток и без номера сообщения 62 строки. Какой-то закономерности не прослеживается, скорее всего это ошибка выгрузки.
# проверим какие сообщения со служебными метками имеют id
df.groupby('subtype')[['client_msg_id']].count()
| client_msg_id | |
|---|---|
| subtype | |
| bot_message | 0 |
| bot_remove | 0 |
| channel_join | 0 |
| channel_leave | 0 |
| channel_name | 0 |
| channel_purpose | 0 |
| channel_unarchive | 0 |
| thread_broadcast | 108 |
| tombstone | 0 |
Только одной категории служебных сообщений присвоены номера сообщений - thread_broadcast. Пропуски в id сообщений оставим без изменений.
# проверим пропуски в столбце 'user'
df[df['user'].isnull()].head()
| client_msg_id | user | ts | latest_reply | team | thread_ts | subtype | channel | file_date | attachments | reactions | text_len | text_words | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 520 | NaN | NaN | 2022-11-28 07:00:57 | NaT | NaN | NaT | bot_message | da_53_exerciser_1 | 2022-11-27 | 0 | NaN | 76 | 12 |
| 521 | NaN | NaN | 2022-11-28 07:00:57 | 2022-11-28 12:34:02 | NaN | 2022-11-28 07:00:57 | bot_message | da_53_exerciser_1 | 2022-11-27 | 0 | NaN | 93 | 15 |
| 522 | NaN | NaN | 2022-11-28 07:00:58 | 2022-12-08 14:12:27 | NaN | 2022-11-28 07:00:58 | bot_message | da_53_exerciser_1 | 2022-11-27 | 0 | NaN | 80 | 13 |
| 523 | NaN | NaN | 2022-11-28 07:00:58 | 2022-12-07 14:14:53 | NaN | 2022-11-28 07:00:58 | bot_message | da_53_exerciser_1 | 2022-11-27 | 0 | NaN | 138 | 18 |
| 524 | NaN | NaN | 2022-11-28 07:00:58 | NaT | NaN | NaT | bot_message | da_53_exerciser_1 | 2022-11-27 | 0 | NaN | 121 | 18 |
# проверим гипотезу: сообщения со служебной меткой 'bot_message' не имеют user id
df[df['user'].isnull()].groupby('subtype')['ts'].count()
subtype bot_message 2890 Name: ts, dtype: int64
Действительно, сообщения от бота не имеют user id. Пропуски в столбце userзаполнять не будем, эта информация нам не понадобится в исследовании.
# проверим наличие явных дубликатов
df.duplicated().sum()
369
# удаляем явные дубликаты
df = df.drop_duplicates()
df.duplicated().sum()
0
# рассмотрим данные в столбце channel - неявных дубликатов нет
set(df.channel)
{'da_42_exerciser_1',
'da_42_exerciser_2',
'da_42_projects_1',
'da_42_projects_2',
'da_50_info',
'da_50_library',
'da_50_teamwork',
'da_52_exerciser',
'da_52_info',
'da_52_library',
'da_52_projects',
'da_52_teamwork',
'da_53_exerciser_1',
'da_53_exerciser_2',
'da_53_info',
'da_53_library',
'da_53_projects_1',
'da_53_projects_2',
'da_53_teamwork',
'da_54_exerciser_01',
'da_54_exerciser_02',
'da_54_info',
'da_54_library',
'da_54_projects_01',
'da_54_projects_02',
'da_54_teamwork',
'da_55_exerciser',
'da_55_info',
'da_55_library',
'da_55_projects',
'da_55_teamwork',
'da_56_exerciser_1',
'da_56_exerciser_2',
'da_56_info',
'da_56_library',
'da_56_projects_1',
'da_56_projects_2',
'da_56_teamwork',
'da_56b_exerciser',
'da_56b_info',
'da_56b_library',
'da_56b_projects',
'da_56b_teamwork',
'da_58_digitalprof',
'da_58_exerciser_1',
'da_58_exerciser_2',
'da_58_exerciser_3',
'da_58_info',
'da_58_library',
'da_58_projects_1',
'da_58_projects_2',
'da_58_projects_3',
'da_58_teamwork',
'da_59_exerciser_01',
'da_59_exerciser_02',
'da_59_exerciser_03',
'da_59_info',
'da_59_library',
'da_59_projects_01',
'da_59_projects_02',
'da_59_projects_03',
'da_59_teamwork',
'da_59b_exerciser',
'da_59b_info',
'da_59b_library',
'da_59b_projects',
'da_59b_teamwork',
'da_60_exerciser_01',
'da_60_exerciser_02',
'da_60_exerciser_03',
'da_60_info',
'da_60_library',
'da_60_projects_01',
'da_60_projects_02',
'da_60_projects_03',
'da_60_teamwork',
'da_60_цифровые-профессии',
'da_61_exerciser_01',
'da_61_exerciser_02',
'da_61_exerciser_03',
'da_61_info',
'da_61_library',
'da_61_project_01',
'da_61_project_02',
'da_61_project_03',
'da_61_teamwork',
'da_61b_exerciser',
'da_61b_info',
'da_61b_projects',
'da_61b_teamwork',
'da_62_b2g',
'da_62_exerciser_1',
'da_62_exerciser_2',
'da_62_info',
'da_62_library',
'da_62_projects_1',
'da_62_projects_2',
'da_62_teamwork',
'da_63_exerciser_1',
'da_63_exerciser_2',
'da_63_info',
'da_63_library',
'da_63_mentors',
'da_63_projects_1',
'da_63_projects_2',
'da_63_teamwork',
'da_63_цифровые_профессии',
'da_bc_02_apps',
'da_bc_02_final_info',
'da_bc_02_telecom',
'da_bc_03_info',
'da_bc_03_library',
'da_bc_03_study',
'da_bc_03_teamwork',
'da_bc_04_info',
'da_bc_04_library',
'da_bc_04_study',
'da_bc_04_teamwork',
'da_plus_09_exerciser',
'da_plus_09_info',
'da_plus_09_kt',
'da_plus_09_library',
'da_plus_09_projects',
'da_plus_09_teamwork',
'da_plus_10_exerciser',
'da_plus_10_info',
'da_plus_10_kt',
'da_plus_10_library',
'da_plus_10_mentors',
'da_plus_10_projects',
'da_plus_10_teamwork',
'da_plus_11_exerciser',
'da_plus_11_info',
'da_plus_11_library',
'da_plus_11_project',
'da_plus_11_teamwork',
'da_plus_12_exerciser',
'da_plus_12_info',
'da_plus_12_kt',
'da_plus_12_library',
'da_plus_12_projects',
'da_plus_12_teamwork',
'da_plus_13_exerciser',
'da_plus_13_info',
'da_plus_13_library',
'da_plus_13_projects',
'da_plus_13_teamwork',
'da_plus_14_exerciser',
'da_plus_14_info',
'da_plus_14_library',
'da_plus_14_projects',
'da_plus_14_teamwork',
'da_plus_15_exerciser',
'da_plus_15_info',
'da_plus_15_library',
'da_plus_15_projects',
'da_plus_15_teamwork',
'da_plus_16_exerciser',
'da_plus_16_info',
'da_plus_16_library',
'da_plus_16_mentors',
'da_plus_16_projects',
'da_plus_16_teamwork',
'da_plus_17_exerciser',
'da_plus_17_info',
'da_plus_17_library',
'da_plus_17_projects',
'da_plus_17_teamwork',
'da_plus_18_exerciser_1',
'da_plus_18_exerciser_2',
'da_plus_18_info',
'da_plus_18_library',
'da_plus_18_projects_1',
'da_plus_18_projects_2',
'da_plus_18_teamwork',
'da_plus_19_exerciser',
'da_plus_19_info',
'da_plus_19_library',
'da_plus_19_mentors',
'da_plus_19_projects',
'da_plus_19_teamwork',
'da_plus_8_info',
'da_plus_8_kt',
'da_plus_8_library',
'da_plus_8_teamwork',
'data-analysts-bus',
'data_complaints',
'data_edteam_info',
'datatracker_logs',
'de-pro1',
'de-pro2',
'de-pro3',
'de-pro4',
'de-project',
'de_1_info',
'de_22_community',
'de_22_exerciser',
'de_22_group_orange',
'de_22_group_red',
'de_22_group_yellow',
'de_22_info',
'de_22_library',
'de_22_projects',
'de_22_tutorial',
'de_23_community',
'de_23_exerciser',
'de_23_group_blue',
'de_23_group_green',
'de_23_group_red',
'de_23_group_yellow',
'de_23_info',
'de_23_library',
'de_23_problem_1',
'de_23_projects',
'de_23_tutorial',
'de_24_community',
'de_24_exerciser',
'de_24_group_green',
'de_24_group_orange',
'de_24_group_red',
'de_24_group_yellow',
'de_24_info',
'de_24_library',
'de_24_projects',
'de_24_topic1',
'de_24_topic2',
'de_24_topic3',
'de_24_tutorial',
'de_25_community',
'de_25_exerciser',
'de_25_group_blue',
'de_25_group_orange',
'de_25_group_purple',
'de_25_group_red',
'de_25_info',
'de_25_library',
'de_25_projects',
'de_25_tutorial',
'de_26_community',
'de_26_exerciser',
'de_26_group_green',
'de_26_group_purple',
'de_26_group_red',
'de_26_group_yellow',
'de_26_info',
'de_26_library',
'de_26_projects',
'de_26_tutorial',
'de_27_2_community',
'de_27_2_digital_professions',
'de_27_2_exerciser',
'de_27_2_group_green',
'de_27_2_group_orange',
'de_27_2_group_yellow',
'de_27_2_info',
'de_27_2_library',
'de_27_2_projects',
'de_27_2_tutorial',
'de_27_community',
'de_27_exerciser',
'de_27_group_green',
'de_27_group_red',
'de_27_group_yellow',
'de_27_info',
'de_27_library',
'de_27_projects',
'de_27_tutorial',
'de_28_2_community',
'de_28_2_digital_professions',
'de_28_2_exerciser',
'de_28_2_group_green',
'de_28_2_group_red',
'de_28_2_group_yellow',
'de_28_2_info',
'de_28_2_library',
'de_28_2_projects',
'de_28_2_tutorial',
'de_28_community',
'de_28_digital_professions',
'de_28_exerciser',
'de_28_group_green',
'de_28_group_red',
'de_28_group_yellow',
'de_28_info',
'de_28_library',
'de_28_projects',
'de_28_tutorial',
'de_29_2_community',
'de_29_2_digital_professions',
'de_29_2_exerciser',
'de_29_2_group_green',
'de_29_2_group_red',
'de_29_2_group_yellow',
'de_29_2_info',
'de_29_2_library',
'de_29_2_mentors',
'de_29_2_projects',
'de_29_2_tutorial',
'de_29_community',
'de_29_digital_professions',
'de_29_exerciser',
'de_29_group_blue',
'de_29_group_purple',
'de_29_group_red',
'de_29_info',
'de_29_library',
'de_29_projects',
'de_29_tutorial',
'de_2_info',
'de_30_community',
'de_30_digital_professions',
'de_30_exerciser',
'de_30_group_green',
'de_30_group_red',
'de_30_group_yellow',
'de_30_info',
'de_30_library',
'de_30_mentors',
'de_30_projects',
'de_30_tutorial',
'de_3_exerciser',
'de_3_info',
'de_3_projects',
'de_4_exerciser',
'de_4_info',
'de_4_projects',
'de_5_exerciser',
'de_5_info',
'de_5_library',
'de_5_mentors',
'de_5_projects',
'de_5_teamwork',
'de_6_exerciser',
'de_6_info',
'de_6_library',
'de_6_mentors',
'de_6_projects',
'de_6_teamwork',
'de_7_exerciser',
'de_7_info',
'de_7_library',
'de_7_mentors',
'de_7_projects',
'de_7_teamwork',
'de_8_exerciser',
'de_8_info',
'de_8_library',
'de_8_mentors',
'de_8_projects',
'de_8_teamwork',
'de_8_workshop_project1',
'de_random_coffee',
'design_web_plus',
'dl_04_community',
'dl_04_info',
'dl_04_students_feedback',
'dl_04_teach_me',
'dl_05_community',
'dl_05_info',
'dl_05_students_feedback',
'dl_05_teach_me',
'dl_06_community',
'dl_06_group_green',
'dl_06_group_yellow',
'dl_06_info',
'dl_06_student_feedback',
'dl_06_teach_me',
'dl_07_community',
'dl_07_info',
'dl_07_student_feedback',
'dl_07_teach_me',
'dl_academ',
'donorsearch',
'ds_35_exerciser_1',
'ds_35_exerciser_2',
'ds_35_exerciser_3',
'ds_35_projects_1',
'ds_35_projects_2',
'ds_35_projects_3',
'ds_38_info',
'ds_38_library',
'ds_38_teamwork',
'ds_40_info',
'ds_40_library',
'ds_40_teamwork',
'ds_42_exerciser',
'ds_42_info',
'ds_42_library',
'ds_42_project',
'ds_42_teamwork',
'ds_43_info',
'ds_43_library',
'ds_43_teamwork',
'ds_44_exerciser',
'ds_44_info',
'ds_44_library',
'ds_44_projects',
'ds_44_teamwork',
'ds_45_exerciser',
'ds_45_info',
'ds_45_library',
'ds_45_projects',
'ds_45_teamwork',
'ds_46_exerciser',
'ds_46_info',
'ds_46_library',
'ds_46_projects',
'ds_46_teamwork',
'ds_47_exerciser',
'ds_47_info',
'ds_47_library',
'ds_47_projects',
'ds_47_teamwork',
'ds_48_exerciser',
'ds_48_info',
'ds_48_library',
'ds_48_projects',
'ds_48_teamwork',
'ds_49_exerciser',
'ds_49_info',
'ds_49_library',
'ds_49_projects',
'ds_49_teamwork',
'ds_50_exerciser_1',
'ds_50_exerciser_2',
'ds_50_info',
'ds_50_library',
'ds_50_projects_1',
'ds_50_projects_2',
'ds_50_teamwork',
'ds_51b_info',
'ds_51b_library',
'ds_51b_projects',
'ds_51b_teamwork',
'ds_54b_exerciser',
'ds_54b_info',
'ds_54b_library',
'ds_54b_projects',
'ds_54b_teamwork',
'ds_55_exerciser_1',
'ds_55_exerciser_2',
'ds_55_exerciser_3',
'ds_55_info',
'ds_55_library',
'ds_55_projects_1',
'ds_55_projects_2',
'ds_55_teamwork',
'ds_57_exerciser_1',
'ds_57_exerciser_2',
'ds_57_info',
'ds_57_library',
'ds_57_projects_1',
'ds_57_projects_2',
'ds_57_teamwork',
'ds_58_exerciser_1',
'ds_58_exerciser_2',
'ds_58_info',
'ds_58_library',
'ds_58_projects_1',
'ds_58_projects_2',
'ds_58_teamwork',
'ds_bc_02_exerciser',
'ds_bc_02_info',
'ds_bc_02_library',
'ds_bc_02_projects',
'ds_bc_02_teamwork',
'ds_bc_03_exerciser',
'ds_bc_03_info',
'ds_bc_03_library',
'ds_bc_03_projects',
'ds_bc_03_teamwork',
'ds_bc_04_exerciser',
'ds_bc_04_info',
'ds_bc_04_library',
'ds_bc_04_projects',
'ds_bc_04_teamwork',
'ds_bc_05_info',
'ds_bc_05_library',
'ds_bc_05_study',
'ds_bc_05_teamwork',
'ds_plus_05_exerciser',
'ds_plus_05_info',
'ds_plus_05_projects',
'ds_plus_05_teamwork',
'ds_plus_06_exerciser',
'ds_plus_06_info',
'ds_plus_06_projects',
'ds_plus_06_teamwork',
'ds_plus_07_exerciser',
'ds_plus_07_info',
'ds_plus_07_projects',
'ds_plus_07_teamwork',
'ds_plus_08_exerciser',
'ds_plus_08_info',
'ds_plus_08_library',
'ds_plus_08_projects',
'ds_plus_08_teamwork',
'ds_plus_09_exerciser',
'ds_plus_09_info',
'ds_plus_09_kt',
'ds_plus_09_library',
'ds_plus_09_projects',
'ds_plus_09_teamwork',
'ds_plus_10_exerciser',
'ds_plus_10_info',
'ds_plus_10_library',
'ds_plus_10_projects',
'ds_plus_10_teamwork',
'ds_plus_11_exerciser',
'ds_plus_11_library',
'ds_plus_11_projects',
'ds_plus_11_teamwork',
'ds_plus_12_exerciser',
'ds_plus_12_info',
'ds_plus_12_library',
'ds_plus_12_projects',
'ds_plus_12_teamwork',
'ds_plus_13_exerciser',
'ds_plus_13_info',
'ds_plus_13_library',
'ds_plus_13_projects',
'ds_plus_13_teamwork',
'ds_plus_14_exerciser',
'ds_plus_14_info',
'ds_plus_14_library',
'ds_plus_14_projects',
'ds_plus_14_teamwork',
'ds_plus_15_exerciser',
'ds_plus_15_info',
'ds_plus_15_library',
'ds_plus_15_projects_1',
'ds_plus_15_projects_2',
'ds_plus_15_teamwork',
'ds_plus_17_exerciser',
'ds_plus_17_info',
'ds_plus_17_library',
'ds_plus_17_mentors',
'ds_plus_17_projects',
'ds_plus_17_teamwork',
'ds_plus_18_exerciser',
'ds_plus_18_info',
'ds_plus_18_library',
'ds_plus_18_projects',
'ds_plus_18_teamwork',
'ds_plus_19_exerciser',
'ds_plus_19_info',
'ds_plus_19_library',
'ds_plus_19_projects',
'ds_plus_19_teamwork',
'masterskaya_10ds_plus',
'masterskaya_10dа_plus',
'masterskaya_11ds_plus',
'masterskaya_11dа_plus',
'masterskaya_12ds_plus',
'masterskaya_7ds_plus',
'masterskaya_8ds_plus',
'masterskaya_8dа_plus',
'masterskaya_9ds_plus',
'masterskaya_9dа_plus',
'sql_04_exerciser',
'sql_04_info',
'sql_04_teamwork',
'sql_exerciser',
'sql_exerciser_03',
'sql_exerciser_new',
'sql_info',
'sql_info_03',
'sql_info_new',
'sql_teamwork',
'sql_teamwork_03',
'sql_teamwork_new'}
# проверим наличие дубликатов в столбце subtype - неявных дубликатов нет
df['subtype'].value_counts()
channel_leave 3265 bot_message 2521 channel_join 2026 thread_broadcast 108 tombstone 14 channel_unarchive 10 channel_purpose 2 bot_remove 1 channel_name 1 Name: subtype, dtype: int64
# рассмотрим распределение и выбросы в столбце text_len
plt.figure(figsize=(15, 5))
sns.boxplot(data=df, x='text_len', color='red')
plt.xlim(0,1000);
# рассмотрим распределение и выбросы в столбце text_words
plt.figure(figsize=(15, 5))
sns.boxplot(data=df, x='text_words', color='red')
plt.xlim(0,200);
Сообщения больше 400 символов и больше 55 слов отмечены как выбросы. Таких сообщений меньше, чем коротких, но это не говорит об аномалии. Многие обяснения могут быть написаны длинными сообщениями. Поэтому оставим этот столбец без изменений.
В результате предобработки мы удалили не несущий информацию столбец type. Выяснили, что большинство пропущенных значений client_id_msg - это сообщения со служебными метками, а также что сообщения от бота не имеют user id. Пропуски в столбцах subtype, client_msg_id, latest_reply, tread_ts, team, user оставили как есть. Удалили явные дубликаты, а также выяснили, что большинство сообщений находятся в пределах 55 слов и 400 символов.
# создадим функцию для выделения типа канала
def channel_type(cell):
'определение канала в столбце channel'
channels = ['info', 'exerciser', 'teamwork', 'projects', 'library', 'masterskaya']
for channel in channels:
if channel in cell:
return channel
return 'other'
# создадим столбец с типом канала используя написанную функцию
df['channel_type'] = df['channel'].apply(channel_type)
df.head()
| client_msg_id | user | ts | latest_reply | team | thread_ts | subtype | channel | file_date | attachments | reactions | text_len | text_words | channel_type | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | ae31e785-257b-4290-a4c6-9721337f67ea | U03JYMWQLP5 | 2022-11-28 13:49:23 | 2022-11-28 14:24:08 | TPV9DP0N4 | 2022-11-28 13:49:23 | NaN | data-analysts-bus | 2022-11-28 | 0 | NaN | 297 | 47 | other |
| 1 | 7f644ae8-16d4-4c9e-9c4f-8b2d6df6a28d | U03V483FRKM | 2022-11-28 14:24:08 | NaT | TPV9DP0N4 | 2022-11-28 13:49:23 | NaN | data-analysts-bus | 2022-11-28 | 0 | [{'name': 'pray', 'users': ['U03JYMWQLP5'], 'c... | 434 | 63 | other |
| 2 | NaN | U02KVQJHQ5S | 2022-11-28 14:48:50 | NaT | NaN | NaT | channel_join | data-analysts-bus | 2022-11-28 | 0 | NaN | 37 | 5 | other |
| 3 | 6c5bf2c1-8579-413c-8e8f-ec3a0e3698a8 | U03JYMWQLP5 | 2022-11-29 08:08:12 | 2022-11-29 10:56:57 | NaN | 2022-11-29 08:08:12 | NaN | data-analysts-bus | 2022-11-29 | 0 | [{'name': 'cat-high-five', 'users': ['U040E2D6... | 69 | 12 | other |
| 4 | b5e3413b-8f04-4192-948b-2423eb3192b2 | U040E2D6CF2 | 2022-11-29 08:32:32 | NaT | TPV9DP0N4 | 2022-11-29 08:08:12 | NaN | data-analysts-bus | 2022-11-29 | 0 | NaN | 19 | 2 | other |
# приведем к единообразию символы в столбце channel
df['channel'] = df['channel'].str.replace('-', '_')
# создадим столбец с выделенным типом группы, используя регулярное выражение
df['group_type'] = df['channel'].str.extract(r'\b(data|da_plus|da_bc|da|de|dl|ds_plus|ds_bc|ds|sql|masterskaya)_')
# заменим пропуски в новом столбце на 'other'
df['group_type'] = df['group_type'].fillna('other')
# проверим результат
df['group_type'].value_counts()
da 10320 ds 7156 de 1981 da_plus 1946 ds_plus 1808 dl 1325 ds_bc 672 da_bc 468 sql 207 data 206 masterskaya 71 other 4 Name: group_type, dtype: int64
# выделим номер когорты с помощью регулярного выражения
df['cohort'] = df['channel'].str.extract(r'_(\d+|\d+b)_')
# заполним пропуски
df['cohort'] = df['cohort'].fillna('0')
# создадим столбец с группой и номером когорты
df['group_cohort'] = df['group_type']+('_')+df['cohort']
# заполняем пропуски в столбце reactions
df['reactions'] = df['reactions'].fillna(0)
# пишем функцию для подсчета суммы реакций
def count_of_reactions(row):
if row != 0:
row = ast.literal_eval(row)
return sum(item['count'] for item in row)
else:
pass
# применим функцию для создания нового столбца. Преобразуем данные в тип int и заполним пропуски 0
df['count_of_reactions'] = df['reactions'].apply(count_of_reactions).fillna(0).astype('int')
df.head()
| client_msg_id | user | ts | latest_reply | team | thread_ts | subtype | channel | file_date | attachments | reactions | text_len | text_words | channel_type | group_type | cohort | group_cohort | count_of_reactions | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | ae31e785-257b-4290-a4c6-9721337f67ea | U03JYMWQLP5 | 2022-11-28 13:49:23 | 2022-11-28 14:24:08 | TPV9DP0N4 | 2022-11-28 13:49:23 | NaN | data_analysts_bus | 2022-11-28 | 0 | 0 | 297 | 47 | other | data | 0 | data_0 | 0 |
| 1 | 7f644ae8-16d4-4c9e-9c4f-8b2d6df6a28d | U03V483FRKM | 2022-11-28 14:24:08 | NaT | TPV9DP0N4 | 2022-11-28 13:49:23 | NaN | data_analysts_bus | 2022-11-28 | 0 | [{'name': 'pray', 'users': ['U03JYMWQLP5'], 'c... | 434 | 63 | other | data | 0 | data_0 | 1 |
| 2 | NaN | U02KVQJHQ5S | 2022-11-28 14:48:50 | NaT | NaN | NaT | channel_join | data_analysts_bus | 2022-11-28 | 0 | 0 | 37 | 5 | other | data | 0 | data_0 | 0 |
| 3 | 6c5bf2c1-8579-413c-8e8f-ec3a0e3698a8 | U03JYMWQLP5 | 2022-11-29 08:08:12 | 2022-11-29 10:56:57 | NaN | 2022-11-29 08:08:12 | NaN | data_analysts_bus | 2022-11-29 | 0 | [{'name': 'cat-high-five', 'users': ['U040E2D6... | 69 | 12 | other | data | 0 | data_0 | 1 |
| 4 | b5e3413b-8f04-4192-948b-2423eb3192b2 | U040E2D6CF2 | 2022-11-29 08:32:32 | NaT | TPV9DP0N4 | 2022-11-29 08:08:12 | NaN | data_analysts_bus | 2022-11-29 | 0 | 0 | 19 | 2 | other | data | 0 | data_0 | 0 |
# выделим время создания, день недели, день месяца, месяц сообщения и дату
df['msg_hour'] = pd.DatetimeIndex(df['ts']).hour
df['msg_weekday'] = pd.DatetimeIndex(df['ts']).weekday
df['msg_day'] = pd.DatetimeIndex(df['ts']).day
df['msg_month'] = pd.DatetimeIndex(df['ts']).month
df['msg_date'] = pd.DatetimeIndex(df['ts']).date
df.head()
| client_msg_id | user | ts | latest_reply | team | thread_ts | subtype | channel | file_date | attachments | ... | channel_type | group_type | cohort | group_cohort | count_of_reactions | msg_hour | msg_weekday | msg_day | msg_month | msg_date | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | ae31e785-257b-4290-a4c6-9721337f67ea | U03JYMWQLP5 | 2022-11-28 13:49:23 | 2022-11-28 14:24:08 | TPV9DP0N4 | 2022-11-28 13:49:23 | NaN | data_analysts_bus | 2022-11-28 | 0 | ... | other | data | 0 | data_0 | 0 | 13 | 0 | 28 | 11 | 2022-11-28 |
| 1 | 7f644ae8-16d4-4c9e-9c4f-8b2d6df6a28d | U03V483FRKM | 2022-11-28 14:24:08 | NaT | TPV9DP0N4 | 2022-11-28 13:49:23 | NaN | data_analysts_bus | 2022-11-28 | 0 | ... | other | data | 0 | data_0 | 1 | 14 | 0 | 28 | 11 | 2022-11-28 |
| 2 | NaN | U02KVQJHQ5S | 2022-11-28 14:48:50 | NaT | NaN | NaT | channel_join | data_analysts_bus | 2022-11-28 | 0 | ... | other | data | 0 | data_0 | 0 | 14 | 0 | 28 | 11 | 2022-11-28 |
| 3 | 6c5bf2c1-8579-413c-8e8f-ec3a0e3698a8 | U03JYMWQLP5 | 2022-11-29 08:08:12 | 2022-11-29 10:56:57 | NaN | 2022-11-29 08:08:12 | NaN | data_analysts_bus | 2022-11-29 | 0 | ... | other | data | 0 | data_0 | 1 | 8 | 1 | 29 | 11 | 2022-11-29 |
| 4 | b5e3413b-8f04-4192-948b-2423eb3192b2 | U040E2D6CF2 | 2022-11-29 08:32:32 | NaT | TPV9DP0N4 | 2022-11-29 08:08:12 | NaN | data_analysts_bus | 2022-11-29 | 0 | ... | other | data | 0 | data_0 | 0 | 8 | 1 | 29 | 11 | 2022-11-29 |
5 rows × 23 columns
Так как сообщения со служебными метками в основном сообщают о том, что кто-то покинул или вступил в канал, логично предположить, что на эти сообщения реакций не ждут и лучше исключить их из анализа воизбежание искажения результата.
# проверим, получают ли реакции служебные сообщения
df[(df['subtype'].notnull())&(df['count_of_reactions']>0)].groupby('subtype')['user'].count()
subtype bot_message 0 channel_join 1 channel_leave 2 thread_broadcast 37 tombstone 2 Name: user, dtype: int64
Cообщения, имеющие метку thread_broadcast, получают реакции и имеют client_msg_id. Метка говорит о том, что сообщенние транслируется на весь канал. Оставим эти сообщения, остальные служебные сообщения удалим.
df = df[(df['subtype'].isnull())|(df['subtype'] == 'thread_broadcast')]
df.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 18324 entries, 0 to 26530 Data columns (total 23 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 client_msg_id 18262 non-null object 1 user 18324 non-null object 2 ts 18324 non-null datetime64[ns] 3 latest_reply 1376 non-null datetime64[ns] 4 team 15765 non-null object 5 thread_ts 17289 non-null datetime64[ns] 6 subtype 108 non-null object 7 channel 18324 non-null object 8 file_date 18324 non-null datetime64[ns] 9 attachments 18324 non-null int64 10 reactions 18324 non-null object 11 text_len 18324 non-null int64 12 text_words 18324 non-null int64 13 channel_type 18324 non-null object 14 group_type 18324 non-null object 15 cohort 18324 non-null object 16 group_cohort 18324 non-null object 17 count_of_reactions 18324 non-null int32 18 msg_hour 18324 non-null int64 19 msg_weekday 18324 non-null int64 20 msg_day 18324 non-null int64 21 msg_month 18324 non-null int64 22 msg_date 18324 non-null object dtypes: datetime64[ns](4), int32(1), int64(7), object(11) memory usage: 3.3+ MB
Мы добавили в таблицу следующие столбцы: channel_type - тип канала, group_type - тип группы, cohort - когорта, group_cohort - группа и когорта, count_of_reactions - количество реакций на пост, msg_hour - время сообщения, msg_weekday - день недели сообщения, msg_day - день месяца сообщения, msg_month - месяц сообщения, msg_date - дату сообщения. Также из таблицы были удалены служебные сообщения, не несущие информации для анализа.
# посморим диапазон дат представленных сообщений
min_ts = df['ts'].min()
max_ts = df['ts'].max()
print('Дата публикации первого сообщения ', min_ts,)
print('Дата публикации последнего сообщения ', max_ts)
Дата публикации первого сообщения 2022-10-21 09:00:00 Дата публикации последнего сообщения 2022-12-25 11:33:22
Для анализа предоставлены данные чуть больше чем за 2 месяца - с 21.10.22 по 25.12.22 гг.
# распределение по месяцам
count_of_msg_month = df.groupby('msg_month')[['user']].count()
count_of_msg_month
| user | |
|---|---|
| msg_month | |
| 10 | 3 |
| 11 | 4256 |
| 12 | 14065 |
plt.figure(figsize=(14, 3))
df.groupby('ts').agg({'ts':'count'}).plot(kind='line')
plt.show()
<Figure size 1008x216 with 0 Axes>
Большинство сообщений опубликовано в декабре.
# распределение количества сообщений по группам
plt.figure(figsize=(15, 5))
with sns.color_palette('Set2'):
ax = sns.countplot(data=df, x='group_type', order=df['group_type'].value_counts().index)
ax.set_title('Количество сообщений в разных типах групп')
ax.set_xlabel('Тип группы')
ax.set_ylabel('Количество')
sns.despine();
Больше всего сообщений генерируют группы da, что логично - их больше всего когорт.
# распределение количества сообщений по каналам
plt.figure(figsize=(15, 5))
with sns.color_palette('Set2'):
ax = sns.countplot(data=df, x='channel_type', order=df['channel_type'].value_counts().index)
ax.set_title('Количество сообщений в разных типах каналов')
ax.set_xlabel('Тип канала')
ax.set_ylabel('Количество')
plt.xticks (rotation = 30)
sns.despine();
Больше всего сообщений в канале projects, что объясняется самостоятельной работой над проектами и как следствие большим количеством вопросов.
# длина сообщений в каналах
fig = px.box(df,
x='channel_type',
y='text_len',
color='channel_type',
notched=True) # вырез у медианы
fig.update_layout(title='Медиана длины сообщений по типам каналов',
xaxis_title='Тип канала',
yaxis_title='Количество символов', yaxis_range=[0, 400],
legend_title_text='Тип канала')
fig.update_xaxes(categoryorder='array', categoryarray=\
df.groupby('channel_type')['text_len'].median().sort_values(ascending=False).index)
fig.show()
Cамые длинные сообщения встречаются в канале library - в нем публикуются дополнительные тематическе материаламы: статьи, ссылки на учебники и полезные книги. Далее каналы projects и exerciser, где публикуются вопросы и объяснения к ним. Каналы info, teamwork и other имеют примерно одинаковое медианное количество символов в сообщениях.
# длина сообщений по типам групп
fig = px.box(df,
x='group_type',
y='text_len',
color='group_type')
fig.update_layout(title='Медиана длины сообщений по типам групп',
xaxis_title='Тип группы',
yaxis_title='Количество символов', yaxis_range=[0, 400],
legend_title_text='Тип группы')
fig.update_xaxes(categoryorder='array', categoryarray=\
df.groupby('group_type')['text_len'].median().sort_values(ascending=False).index)
fig.show()
Самые многословные группы - это студенты дата аналитик буткемп - короткосрочной программы анализа данных. Далее идут студенты расширенных групп (plus). Самые короткие сообщения пишут в канале мастерской.
# добавим столбец, указывающий что пост имеет хотя бы реакцию
# напишем функцию для метки столбца
def reaction(number):
if number > 0:
return 'пост с реакцией'
return 'пост без реакции'
df['is_reaction'] = df['count_of_reactions'].apply(reaction)
# проведем группировку для графика
react = df.groupby('is_reaction')[['user']].count()
react
| user | |
|---|---|
| is_reaction | |
| пост без реакции | 14219 |
| пост с реакцией | 4105 |
# круговая диаграмма доли постов с наличием хотя бы реакции
fig = px.pie(react, values='user', names=react.index, color_discrete_sequence=px.colors.sequential.Burgyl, \
title='Доля постов с реакциями', hole=.6)
fig.update_traces(textposition='inside', textinfo='percent+label')
fig.update_layout(showlegend=False)
fig.show()
Как видно из диаграммы, только 22,4% постов получают хотя бы одну реакцию, что довольно немного.
# рассмотрим общее распределение сообщений в течение дня
count_of_msg = df.groupby('msg_hour')[['user']].count()
plt.figure(figsize=(15, 5))
sns.lineplot(x=count_of_msg.index, y='user', data=count_of_msg, marker='D', color='red')
plt.title('Распределение выхода сообщений по времени дня')
plt.xlabel('Время дня')
plt.ylabel('Количество сообщений')
# украшения ревьюера
plt.axvline(x=7, linestyle='--', linewidth=2, color='green')
plt.axvline(x=18, linestyle='--', linewidth=2, color='green')
plt.annotate('Максимальная активность', xy=(11, 1000))
import numpy as np
plt.xticks(np.arange(0, 23, step=1)) # шкалу часов немного поменял внизу
plt.show()
Самое большое количество сообщений выходит в 13:00 и в 15:00. В остальном в течение дня выход сообщений распределен более менее равномерно с 7:00 до 20:00, но к концу дня публикаций меньше.
# рассмотрим общее распределение сообщений в течение недели
count_of_msg_week = df.groupby('msg_weekday')[['user']].count()
plt.figure(figsize=(15, 5))
sns.lineplot(x=count_of_msg_week.index, y='user', data=count_of_msg_week, marker='D', color='red')
plt.title('Распределение выхода сообщений в течение недели')
plt.xlabel('День недели')
plt.ylabel('Количество сообщений')
plt.show()
Больше всего сообщений генерируется в понедельник. Второй пик - в четверг, перед концом недели. Меньше всего в субботу.
# рассмотрим общее распределение сообщений в течение срока предоставленных данных
count_of_msg_date = df.groupby('msg_date')[['user']].count()
plt.figure(figsize=(15, 5))
sns.lineplot(x=count_of_msg_date.index, y='user', data=count_of_msg_date, marker='D', color='red')
plt.title('Распределение выхода сообщений в течение срока предоставленных данных')
plt.xlabel('День недели')
plt.ylabel('Количество сообщений')
plt.show()
Основное количество сообщений опубликовано с 28 ноября 17 декабря 2022 - это примерно 3 недели. Возможно время активности обусловлено проведением спринта в большинстве когорт, представленных для анализа.
# сгруппируем данные по времени выхода поста
mean_of_react_hour = df.groupby('msg_hour')[['count_of_reactions']].mean()
# построим график зависимости количества реакций от времени выхода поста
plt.figure(figsize=(15, 5))
sns.lineplot(x=mean_of_react_hour.index, y='count_of_reactions', data=mean_of_react_hour, marker='D', color='green')
plt.title('Среднее количество реакций на сообщения в зависимости от времени выхода поста')
plt.xlabel('Время выхода поста')
plt.ylabel('Среднее количество сообщений')
plt.show()
Больше всего реакций получают посты, выходящие в 7:00. Это время подъема или выхода на работу большинства людей. Второй пик - 13:00 - время обеда - но тут среднее количество реакций существенно ниже. И небольшое увеличение активности есть вечером около 17:00 - возможно конец рабочего дня, дорога домой.
# сгруппируем данные по дню недели выхода поста
mean_of_react_weekday = df.groupby('msg_weekday')[['count_of_reactions']].mean()
# построим график зависимости количества реакций от дня недели выхода поста
plt.figure(figsize=(15, 5))
sns.lineplot(x=mean_of_react_weekday.index, y='count_of_reactions', data=mean_of_react_weekday, marker='D', color='blue')
plt.title('Среднее количество реакций на сообщения в зависимости от дня недели выхода поста')
plt.xlabel('День недели (0 - понедельник, 6 - воскресенье)')
plt.ylabel('Среднее количество сообщений')
plt.show()
Большее количество реакций получают посты, выходящие в понедельник - люди настроены на новую неделю после выходных и еще не загружены работой. Меньше всего реакций в воскресенье - выходной, большинству не до учебных чатов.
# сгруппируем данные по дню месяца выхода поста
mean_of_react_day = df.groupby('msg_day')[['count_of_reactions']].mean()
# построим график зависимости количества реакций от дня месяца выхода поста
plt.figure(figsize=(15, 5))
sns.lineplot(x=mean_of_react_day.index, y='count_of_reactions', data=mean_of_react_day, marker='D', color='orange')
plt.title('Среднее количество реакций на сообщения в зависимости от дня месяца выхода поста')
plt.xlabel('День месяца')
plt.ylabel('Среднее количество сообщений')
plt.show()
Наибольшая активность наблюдается в середине и конце месяца, в первых числах выходило много постов (так как основной период анализа 3 недели - с 28 ноября по 17 декабря, предположительно недели спринта) и результат расчета среднего падает.
У нас имеются данные, когда был создан тред, к которому относится сообщение. Вычислив разницу во времени мы узнаем, через какое время после создания треда было отправлено сообщение, содержащееся в нем.
# вычисляем сколько прошло времени между созданием треда и отправкой сообщения
df['lag'] = df['ts'] - df['thread_ts']
df.head()
| client_msg_id | user | ts | latest_reply | team | thread_ts | subtype | channel | file_date | attachments | ... | cohort | group_cohort | count_of_reactions | msg_hour | msg_weekday | msg_day | msg_month | msg_date | is_reaction | lag | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | ae31e785-257b-4290-a4c6-9721337f67ea | U03JYMWQLP5 | 2022-11-28 13:49:23 | 2022-11-28 14:24:08 | TPV9DP0N4 | 2022-11-28 13:49:23 | NaN | data_analysts_bus | 2022-11-28 | 0 | ... | 0 | data_0 | 0 | 13 | 0 | 28 | 11 | 2022-11-28 | пост без реакции | 0 days 00:00:00 |
| 1 | 7f644ae8-16d4-4c9e-9c4f-8b2d6df6a28d | U03V483FRKM | 2022-11-28 14:24:08 | NaT | TPV9DP0N4 | 2022-11-28 13:49:23 | NaN | data_analysts_bus | 2022-11-28 | 0 | ... | 0 | data_0 | 1 | 14 | 0 | 28 | 11 | 2022-11-28 | пост с реакцией | 0 days 00:34:45 |
| 3 | 6c5bf2c1-8579-413c-8e8f-ec3a0e3698a8 | U03JYMWQLP5 | 2022-11-29 08:08:12 | 2022-11-29 10:56:57 | NaN | 2022-11-29 08:08:12 | NaN | data_analysts_bus | 2022-11-29 | 0 | ... | 0 | data_0 | 1 | 8 | 1 | 29 | 11 | 2022-11-29 | пост с реакцией | 0 days 00:00:00 |
| 4 | b5e3413b-8f04-4192-948b-2423eb3192b2 | U040E2D6CF2 | 2022-11-29 08:32:32 | NaT | TPV9DP0N4 | 2022-11-29 08:08:12 | NaN | data_analysts_bus | 2022-11-29 | 0 | ... | 0 | data_0 | 0 | 8 | 1 | 29 | 11 | 2022-11-29 | пост без реакции | 0 days 00:24:20 |
| 7 | 6a730fa5-7934-41f4-96f0-aa04787bce8e | U03DZHHUACW | 2022-11-29 10:26:46 | 2022-11-29 12:08:02 | TPV9DP0N4 | 2022-11-29 10:26:46 | NaN | data_analysts_bus | 2022-11-29 | 0 | ... | 0 | data_0 | 5 | 10 | 1 | 29 | 11 | 2022-11-29 | пост с реакцией | 0 days 00:00:00 |
5 rows × 25 columns
# примем, что реакция меньше 30 минут считается быстрой
# выделим сообщения, на которые быстро отреагировали
fast_reaction = df[(df['lag'] > '0 days 00:00:00') & (df['lag'] <= '0 days 00:30:00')]
fast_reaction.head()
| client_msg_id | user | ts | latest_reply | team | thread_ts | subtype | channel | file_date | attachments | ... | cohort | group_cohort | count_of_reactions | msg_hour | msg_weekday | msg_day | msg_month | msg_date | is_reaction | lag | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 4 | b5e3413b-8f04-4192-948b-2423eb3192b2 | U040E2D6CF2 | 2022-11-29 08:32:32 | NaT | TPV9DP0N4 | 2022-11-29 08:08:12 | NaN | data_analysts_bus | 2022-11-29 | 0 | ... | 0 | data_0 | 0 | 8 | 1 | 29 | 11 | 2022-11-29 | пост без реакции | 0 days 00:24:20 |
| 31 | 9775dcc2-610d-4b3a-ac86-8e721d47f3d1 | U02Q4P6SGBC | 2022-12-01 13:54:46 | NaT | TPV9DP0N4 | 2022-12-01 13:49:57 | NaN | data_analysts_bus | 2022-12-01 | 0 | ... | 0 | data_0 | 1 | 13 | 3 | 1 | 12 | 2022-12-01 | пост с реакцией | 0 days 00:04:49 |
| 32 | 33078238-d338-4305-9d45-f600b6883b8f | U03JYMWQLP5 | 2022-12-01 14:17:22 | NaT | TPV9DP0N4 | 2022-12-01 13:49:57 | NaN | data_analysts_bus | 2022-12-01 | 0 | ... | 0 | data_0 | 1 | 14 | 3 | 1 | 12 | 2022-12-01 | пост с реакцией | 0 days 00:27:25 |
| 41 | a8938d89-5206-4771-8435-beca0ab21c88 | U03JYMWQLP5 | 2022-12-03 17:07:36 | NaT | NaN | 2022-12-03 16:42:59 | NaN | data_analysts_bus | 2022-12-03 | 0 | ... | 0 | data_0 | 1 | 17 | 5 | 3 | 12 | 2022-12-03 | пост с реакцией | 0 days 00:24:37 |
| 54 | 7a121d41-30e0-4fd7-8584-86d7ff4329e9 | U03JYMWQLP5 | 2022-12-07 03:25:24 | NaT | TPV9DP0N4 | 2022-12-07 03:16:10 | NaN | data_analysts_bus | 2022-12-06 | 0 | ... | 0 | data_0 | 1 | 3 | 2 | 7 | 12 | 2022-12-07 | пост с реакцией | 0 days 00:09:14 |
5 rows × 25 columns
# рассмотрим зависимость быстроты ответов от времени публикации поста
# сгруппируем данные по времени
fast_react_hour = fast_reaction.groupby('msg_hour')[['user']].count()
# добавим столбец с общим количеством сообщений из базовой таблицы
fast_react_hour['total_msg'] = df.groupby('msg_hour')[['user']].count()
# вычислим долю быстрых ответов
fast_react_hour['fast_react_share'] = round(fast_react_hour['user']/fast_react_hour['total_msg']*100,2)
fast_react_hour
| user | total_msg | fast_react_share | |
|---|---|---|---|
| msg_hour | |||
| 0 | 3 | 52 | 5.77 |
| 1 | 1 | 46 | 2.17 |
| 3 | 3 | 99 | 3.03 |
| 4 | 1 | 167 | 0.60 |
| 5 | 11 | 282 | 3.90 |
| 6 | 41 | 511 | 8.02 |
| 7 | 117 | 1148 | 10.19 |
| 8 | 117 | 1274 | 9.18 |
| 9 | 80 | 1217 | 6.57 |
| 10 | 123 | 1216 | 10.12 |
| 11 | 114 | 1305 | 8.74 |
| 12 | 98 | 1375 | 7.13 |
| 13 | 184 | 1465 | 12.56 |
| 14 | 131 | 1348 | 9.72 |
| 15 | 149 | 1365 | 10.92 |
| 16 | 57 | 1145 | 4.98 |
| 17 | 80 | 971 | 8.24 |
| 18 | 67 | 1073 | 6.24 |
| 19 | 95 | 919 | 10.34 |
| 20 | 38 | 625 | 6.08 |
| 21 | 32 | 367 | 8.72 |
| 22 | 4 | 190 | 2.11 |
| 23 | 3 | 95 | 3.16 |
# визуализируем данные
sns.set_style('whitegrid')
plt.figure(figsize=(15, 5))
sns.lineplot(x=fast_react_hour.index, y='fast_react_share', data=fast_react_hour, marker='D', color='violet')
plt.title('График зависимости быстрого ответа (меньше 30 минут) от времени создания треда')
plt.xlabel('Время публикации')
plt.ylabel('Процент постов с быстрым ответом')
plt.show()
Больше всего быстрых ответов получают сообщения, опубликованные в 13:00 и 15:00 - время начала и конца обеда у большинства людей. Также достаточно высокую активность имеют сообщения, вышедшие в 7:00, 10:00, 14:00, 19:00 - время прихода на работу, обеда и возвращения домой.
# рассмотрим зависимость быстроты ответов от дня недели публикации поста
# сгруппируем данные по дню недели
fast_react_weekday = fast_reaction.groupby('msg_weekday')[['user']].count()
# добавим столбец с общим количеством сообщений из базовой таблицы
fast_react_weekday['total_msg'] = df.groupby('msg_weekday')[['user']].count()
# вычислим долю быстрых ответов
fast_react_weekday['fast_react_share'] = round(fast_react_weekday['user']/fast_react_weekday['total_msg']*100,2)
fast_react_weekday
| user | total_msg | fast_react_share | |
|---|---|---|---|
| msg_weekday | |||
| 0 | 433 | 3649 | 11.87 |
| 1 | 276 | 3165 | 8.72 |
| 2 | 157 | 2542 | 6.18 |
| 3 | 304 | 3014 | 10.09 |
| 4 | 184 | 2565 | 7.17 |
| 5 | 77 | 1514 | 5.09 |
| 6 | 118 | 1875 | 6.29 |
# визуализируем данные
sns.set_style('whitegrid')
plt.figure(figsize=(15, 5))
sns.lineplot(x=fast_react_weekday.index, y='fast_react_share', data=fast_react_weekday, marker='D', color='violet')
plt.title('График зависимости быстрого ответа (меньше 30 минут) от дня недели создания треда')
plt.xlabel('День недели 0 - понедельник, 6 - воскресенье')
plt.ylabel('Процент постов с быстрым ответом')
plt.show()
Большее количество быстрых ответов получают посты, опубликованные в понедельник. Вторые по откликам - посты, опубликованные в четверг.
# рассмотрим зависимость быстроты ответов от дня месяца публикации поста
# сгруппируем данные по дню месяца
fast_react_day = fast_reaction.groupby('msg_day')[['user']].count()
# добавим столбец с общим количеством сообщений из базовой таблицы
fast_react_day['total_msg'] = df.groupby('msg_day')[['user']].count()
# вычислим долю быстрых ответов
fast_react_day['fast_react_share'] = round(fast_react_day['user']/fast_react_day['total_msg']*100,2)
fast_react_day
| user | total_msg | fast_react_share | |
|---|---|---|---|
| msg_day | |||
| 1 | 187 | 1510 | 12.38 |
| 2 | 107 | 1386 | 7.72 |
| 3 | 55 | 825 | 6.67 |
| 4 | 47 | 974 | 4.83 |
| 5 | 97 | 1380 | 7.03 |
| 6 | 68 | 1324 | 5.14 |
| 7 | 53 | 1149 | 4.61 |
| 8 | 89 | 1276 | 6.97 |
| 9 | 66 | 1098 | 6.01 |
| 10 | 22 | 668 | 3.29 |
| 11 | 55 | 716 | 7.68 |
| 12 | 104 | 859 | 12.11 |
| 13 | 21 | 411 | 5.11 |
| 14 | 15 | 249 | 6.02 |
| 15 | 24 | 218 | 11.01 |
| 21 | 1 | 3 | 33.33 |
| 23 | 1 | 11 | 9.09 |
| 24 | 4 | 6 | 66.67 |
| 25 | 10 | 25 | 40.00 |
| 27 | 16 | 182 | 8.79 |
| 28 | 232 | 1403 | 16.54 |
| 29 | 187 | 1418 | 13.19 |
| 30 | 88 | 1127 | 7.81 |
# визуализируем данные
sns.set_style('whitegrid')
plt.figure(figsize=(15, 5))
sns.lineplot(x=fast_react_day.index, y='fast_react_share', data=fast_react_day, marker='D', color='violet')
plt.title('График зависимости быстрого ответа (меньше 30 минут) от дня месяца создания треда')
plt.xlabel('День месяца')
plt.ylabel('Процент постов с быстрым ответом')
plt.show()
График неравномерный, видно аномально высокую активность с середины месяца до 26 числа. Это связано с маленькой выборкой данных за эти даты, так как большинство сообщений было опубликовано с 28 ноября по 17 декабря. В эти даты пики это 28 ноября - судя по всему дата начала спринта (понедельник), 1 декабря, 12 декабря - то есть в конце месяца, в начале и в середине.
# выделим сообщения хотя бы с 1 реакцией в отдельный датасет
is_react = df[df['count_of_reactions'] > 0]
is_react.head()
| client_msg_id | user | ts | latest_reply | team | thread_ts | subtype | channel | file_date | attachments | ... | cohort | group_cohort | count_of_reactions | msg_hour | msg_weekday | msg_day | msg_month | msg_date | is_reaction | lag | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 7f644ae8-16d4-4c9e-9c4f-8b2d6df6a28d | U03V483FRKM | 2022-11-28 14:24:08 | NaT | TPV9DP0N4 | 2022-11-28 13:49:23 | NaN | data_analysts_bus | 2022-11-28 | 0 | ... | 0 | data_0 | 1 | 14 | 0 | 28 | 11 | 2022-11-28 | пост с реакцией | 0 days 00:34:45 |
| 3 | 6c5bf2c1-8579-413c-8e8f-ec3a0e3698a8 | U03JYMWQLP5 | 2022-11-29 08:08:12 | 2022-11-29 10:56:57 | NaN | 2022-11-29 08:08:12 | NaN | data_analysts_bus | 2022-11-29 | 0 | ... | 0 | data_0 | 1 | 8 | 1 | 29 | 11 | 2022-11-29 | пост с реакцией | 0 days 00:00:00 |
| 7 | 6a730fa5-7934-41f4-96f0-aa04787bce8e | U03DZHHUACW | 2022-11-29 10:26:46 | 2022-11-29 12:08:02 | TPV9DP0N4 | 2022-11-29 10:26:46 | NaN | data_analysts_bus | 2022-11-29 | 0 | ... | 0 | data_0 | 5 | 10 | 1 | 29 | 11 | 2022-11-29 | пост с реакцией | 0 days 00:00:00 |
| 9 | 031b6894-db20-4844-91e4-7c6324351365 | U040E2D6CF2 | 2022-11-29 10:56:57 | NaT | TPV9DP0N4 | 2022-11-29 08:08:12 | NaN | data_analysts_bus | 2022-11-29 | 0 | ... | 0 | data_0 | 1 | 10 | 1 | 29 | 11 | 2022-11-29 | пост с реакцией | 0 days 02:48:45 |
| 14 | da48fa1a-4632-4aab-a22c-ca0a5ef29d34 | U03DZHHUACW | 2022-11-29 11:56:44 | NaT | TPV9DP0N4 | 2022-11-29 10:26:46 | NaN | data_analysts_bus | 2022-11-29 | 0 | ... | 0 | data_0 | 1 | 11 | 1 | 29 | 11 | 2022-11-29 | пост с реакцией | 0 days 01:29:58 |
5 rows × 25 columns
# сгруппируем данные по типу группы
group_react = is_react.groupby('group_type')[['count_of_reactions']].count()
# добавим столбец с общим количеством сообщений по группам
group_react['count_of_msg'] = df.groupby('group_type')['user'].count()
# рассчитаем долю сообщений с хотя бы 1 реакцией
group_react['react_share'] = round(group_react['count_of_reactions'] / group_react['count_of_msg']*100,2)
# отсортируем по убыванию доли
group_react = group_react.sort_values(by='react_share', ascending=False)
group_react
| count_of_reactions | count_of_msg | react_share | |
|---|---|---|---|
| group_type | |||
| masterskaya | 27 | 59 | 45.76 |
| data | 86 | 202 | 42.57 |
| de | 350 | 1298 | 26.96 |
| da | 1585 | 6852 | 23.13 |
| dl | 261 | 1155 | 22.60 |
| da_plus | 308 | 1396 | 22.06 |
| ds_plus | 284 | 1306 | 21.75 |
| ds | 1028 | 5068 | 20.28 |
| da_bc | 84 | 426 | 19.72 |
| ds_bc | 68 | 396 | 17.17 |
| sql | 24 | 166 | 14.46 |
# визуализируем данные
fig = px.bar(group_react, x=group_react.index, y='react_share', color=group_react.index,\
title='Доля сообщений с реакциями с разбивкой по типам групп',\
color_discrete_sequence=px.colors.qualitative.Set2,\
text='react_share')
fig.update_layout(showlegend=False,
xaxis_title='Тип группы',
yaxis_title='Процент сообщений')
fig.show()
Самые часто реагируемые сообщения в группах masterskaya и data. Самые мало реагируемые сообщения в группах sql. В остальных группах процент реакций находится примерно на уровне 20%. Чтобы учесть общее количество сообщений рассмотрим также среднее количество реакций на каждый пост по типу группы.
# визуализируем среднее количество реакций на каждый пост по типу группы
plt.figure(figsize=(12, 5))
with sns.color_palette('Set2'):
ax = sns.barplot(data=df, x='count_of_reactions', y='group_type', orient='h',\
order=df.groupby('group_type')['count_of_reactions'].mean().\
sort_values(ascending=False).index)
ax.set_title('Среднее количество реакций на 1 пост по группам')
ax.set_xlabel('Количество реакций на 1 сообщение')
ax.set_ylabel('Тип группы')
sns.despine()
Среднее количество реакций на пост равно 1 в группах data. В самых многочисленных по количеству сообщений группах da и ds среднее количество реакций на пост равно 0,65 и 0,42 соответственно. Как и в предущих расчетах меньше всего реакций в группах sql.
# сгруппируем данные по типу канала
channel_react = is_react.groupby('channel_type')[['count_of_reactions']].count()
# добавим столбец с общим количеством сообщений по каналам
channel_react['count_of_msg'] = df.groupby('channel_type')['user'].count()
# рассчитаем долю сообщений с хотя бы 1 реакцией
channel_react['react_share'] = round(channel_react['count_of_reactions'] / channel_react['count_of_msg']*100,2)
# отсортируем по убыванию доли
channel_react = channel_react.sort_values(by='react_share', ascending=False)
channel_react
| count_of_reactions | count_of_msg | react_share | |
|---|---|---|---|
| channel_type | |||
| library | 25 | 39 | 64.10 |
| masterskaya | 27 | 59 | 45.76 |
| info | 878 | 2823 | 31.10 |
| other | 592 | 2409 | 24.57 |
| teamwork | 733 | 3346 | 21.91 |
| exerciser | 871 | 4036 | 21.58 |
| projects | 979 | 5612 | 17.44 |
# визуализируем данные
fig = px.bar(channel_react, x=channel_react.index, y='react_share', color=channel_react.index,\
title='Доля сообщений с реакциями с разбивкой по типам каналов',\
color_discrete_sequence=px.colors.qualitative.Set2,\
text='react_share')
fig.update_layout(showlegend=False,
xaxis_title='Тип канала',
yaxis_title='Процент сообщений')
fig.show()
Процент реакций в канале library - 64% - очень хороший. По всей видимости структурированная дополнительная информация воспринимается хорошо и имеет большой отклик. Сообщения в канале masterskaya также имеют достаточно высокий отклик - почти 46% сообщений имеют хотя бы 1 реакцию. Меньше всего реакций в канале projects - туда заходят задать вопрос или найти ответ, редко у кого возникает желание поставить реакцию.
# визуализируем среднее количество реакций на каждый пост по типу канала
plt.figure(figsize=(12, 5))
with sns.color_palette('Set2'):
ax = sns.barplot(data=df, x='count_of_reactions', y='channel_type', orient='h',\
order=df.groupby('channel_type')['count_of_reactions'].mean().\
sort_values(ascending=False).index)
ax.set_title('Среднее количество реакций на 1 пост по каналам')
ax.set_xlabel('Количество реакций на 1 сообщение')
ax.set_ylabel('Тип канала')
sns.despine()
Сообщения в канале library имеют в среднем больше 4 реакций на пост - это очень хороший показатель. Сообщения в канале info - около 2 реакций на пост. Учитывая, что в этом канале чаще всего публикуются анонсы мероприятий, это довольно мало. Остальные каналы имеют меньше 1 реакции на пост.
В целом, судя по таблице channel_react, можно предположить, что чем больше сообщений в группе, тем меньше процент отклика.
plt.figure(figsize=(15, 5))
sns.set_style('whitegrid',
{'axes.facecolor': '0.8',
'grid.color': '0.2',
'figure.facecolor': '0.7'})
sns.scatterplot(x='text_len', y='count_of_reactions', data=is_react)
plt.title('График зависимости количества реакций от количества символов')
plt.xlabel('Количество символов в сообщении')
plt.ylabel('Количество реакций')
plt.show()
plt.figure(figsize=(15, 5))
sns.set_style('whitegrid',
{'axes.facecolor': '0.8',
'grid.color': '0.2',
'figure.facecolor': '0.7'})
sns.scatterplot(x='text_words', y='count_of_reactions', data=is_react)
plt.title('График зависимости количества реакций от количества слов')
plt.xlabel('Количество слов в сообщении')
plt.ylabel('Количество реакций')
plt.show()
В целом можно сказать, что чем меньше сообщение по длине, тем более вероятно, что его дочитают до конца и что у него будут реакции. Но и коротких сообщений с нулевым реагированием достаточно.
info¶Выявленная проблема от заказчика - отсутствия реакций на важные анонсы. Анонсы чаще всего делают кураторы и наставники. Канал info - основной канал общения кураторов со студентами. Кроме того, по правилам этого канала сообщения в него могут писать только куратор и наставник, студенты должны отвечать в тредах. Проанализируем отдельно активность в этом канале.
Мы знаем, что в таблице в этом канале преедставлено 2823 сообщения, медиана сообщения около 100 символов. Около 25% сообщений в этом канале имеют хотя бы 1 реакцию и среднее количество реакций на один пост около 2.
# выделим в отдельную таблицу сообщения канала info
info = df[df['channel_type'] == 'info']
# выделим сообщения, не относящиеся к треду
# это и будут предположительно сообщения от кураторов и наставников
t_info = info[info['thread_ts'].isnull()]
t_info.head()
| client_msg_id | user | ts | latest_reply | team | thread_ts | subtype | channel | file_date | attachments | ... | cohort | group_cohort | count_of_reactions | msg_hour | msg_weekday | msg_day | msg_month | msg_date | is_reaction | lag | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 170 | a5c6ff4f-f4e6-4d97-8e9a-a2675b68f5b7 | U0104BRNQG1 | 2022-11-28 09:30:14 | NaT | TPV9DP0N4 | NaT | NaN | data_edteam_info | 2022-11-28 | 0 | ... | 0 | data_0 | 6 | 9 | 0 | 28 | 11 | 2022-11-28 | пост с реакцией | NaT |
| 171 | 5657aaec-25c7-44e1-b2ac-ff0867f81149 | U01DVNLSSBZ | 2022-11-28 15:28:09 | NaT | TPV9DP0N4 | NaT | NaN | data_edteam_info | 2022-11-28 | 0 | ... | 0 | data_0 | 2 | 15 | 0 | 28 | 11 | 2022-11-28 | пост с реакцией | NaT |
| 172 | 2D934809-3B8D-4BAC-A756-BED3DD40C4B3 | UTZ0ZG8TB | 2022-11-30 12:40:02 | NaT | TPV9DP0N4 | NaT | NaN | data_edteam_info | 2022-11-30 | 0 | ... | 0 | data_0 | 0 | 12 | 2 | 30 | 11 | 2022-11-30 | пост без реакции | NaT |
| 173 | 5e14f061-fed4-46e0-bd01-cff41c271fc0 | U01733EE0G7 | 2022-11-30 13:18:14 | NaT | TPV9DP0N4 | NaT | NaN | data_edteam_info | 2022-11-30 | 0 | ... | 0 | data_0 | 0 | 13 | 2 | 30 | 11 | 2022-11-30 | пост без реакции | NaT |
| 174 | c2fa117f-499b-4d75-863c-ee23a5fc1a43 | U01DVNLSSBZ | 2022-12-01 08:52:18 | NaT | TPV9DP0N4 | NaT | NaN | data_edteam_info | 2022-12-01 | 0 | ... | 0 | data_0 | 0 | 8 | 3 | 1 | 12 | 2022-12-01 | пост без реакции | NaT |
5 rows × 25 columns
# проведем группировку для графика
t_info_react = t_info.groupby('is_reaction')[['user']].count()
t_info_react
| user | |
|---|---|
| is_reaction | |
| пост без реакции | 314 |
| пост с реакцией | 189 |
# круговая диаграмма доли постов с наличием хотя бы реакции
fig = px.pie(t_info_react, values='user', names=t_info_react.index, color_discrete_sequence=px.colors.sequential.Burgyl, \
title='Доля постов с реакциями в канале info')
fig.update_traces(textposition='inside', textinfo='percent+label')
fig.update_layout(showlegend=False)
fig.show()
Количество постов с реакциями около 40% - для анонсов не самый ожидаемый отклик.
# рассмотрим распределение и выбросы в столбце text_len
plt.figure(figsize=(15, 5))
with sns.color_palette('Set2'):
sns.boxplot(data=t_info, x='count_of_reactions');
Большинство постов имеет до 2 реакций. Посты с больше чем 5 реакциями уже отмечены как выбросы.
# визуализируем среднее количество реакций на каждый пост по типу группы
plt.figure(figsize=(12, 5))
with sns.color_palette('Set2'):
ax = sns.barplot(data=t_info, x='count_of_reactions', y='group_type', orient='h',\
order=t_info.groupby('group_type')['count_of_reactions'].mean().\
sort_values(ascending=False).index)
ax.set_title('Среднее количество реакций на 1 пост по группам в канале info')
ax.set_xlabel('Количество реакций на 1 сообщение')
ax.set_ylabel('Тип группы')
sns.despine()
Самые активные группы - da_bc и ds - больше 4 реакций в среднем на один пост куратора. Самая малоактивная группа - sql - меньше 1 реакции на пост.
# рассмотрим количество реакций по когортам
# выделим сообщения с хотя бы 1 реакцией и сгруппируем по когортам
info_gr = info[info['count_of_reactions']>0].groupby('group_cohort')[['count_of_reactions']].count()
# добавим столбец с общим количеством сообщений в канале info
info_gr['msg_cnt'] = info.groupby('group_cohort')[['user']].count()
# рассчитаем долю сообщений с реакциями
info_gr['react_share'] = round(info_gr['count_of_reactions']/ info_gr['msg_cnt']*100,2)
# отсортируем результат
info_gr = info_gr.sort_values(by='react_share', ascending=False)
# выделим первые 15 когорт по доли реакции
info_gr_head = info_gr.head(15)
info_gr_head
| count_of_reactions | msg_cnt | react_share | |
|---|---|---|---|
| group_cohort | |||
| ds_40 | 1 | 1 | 100.00 |
| de_25 | 1 | 1 | 100.00 |
| da_bc_04 | 10 | 12 | 83.33 |
| da_plus_18 | 18 | 24 | 75.00 |
| da_bc_02 | 7 | 10 | 70.00 |
| ds_plus_17 | 17 | 27 | 62.96 |
| ds_57 | 20 | 36 | 55.56 |
| ds_44 | 15 | 28 | 53.57 |
| ds_plus_08 | 11 | 21 | 52.38 |
| ds_plus_12 | 13 | 25 | 52.00 |
| data_0 | 19 | 37 | 51.35 |
| de_7 | 1 | 2 | 50.00 |
| ds_plus_15 | 27 | 55 | 49.09 |
| ds_50 | 25 | 52 | 48.08 |
| da_61 | 50 | 108 | 46.30 |
# визуализируем результат
fig = px.bar(info_gr_head, x=info_gr_head.index, y='react_share', color=info_gr_head.index,\
title='Доля сообщений с реакциями в канале инфо по когортам',\
color_discrete_sequence=px.colors.qualitative.Set2,\
text='react_share')
fig.update_layout(showlegend=False,
xaxis_title='Тип группы и когорта',
yaxis_title='Процент сообщений')
fig.show()
Если не рассматривать когорты с 1 сообщением в канале, то активность в некоторых доходит до 80%, что обнадеживает - значит в других когортах можно достичь подобного результата, если подробнее проанализировать сообщения. К сожалению, а данном датасете для этого недостаточно данных.
# выделим последние 15 когорт по доли реакции
info_gr_tail = info_gr.tail(15)
info_gr_tail
| count_of_reactions | msg_cnt | react_share | |
|---|---|---|---|
| group_cohort | |||
| ds_plus_06 | 2 | 11 | 18.18 |
| ds_51b | 1 | 6 | 16.67 |
| da_53 | 7 | 45 | 15.56 |
| de_22 | 2 | 14 | 14.29 |
| da_56b | 2 | 16 | 12.50 |
| da_50 | 2 | 20 | 10.00 |
| ds_46 | 5 | 53 | 9.43 |
| da_plus_11 | 1 | 12 | 8.33 |
| da_61b | 3 | 39 | 7.69 |
| ds_plus_05 | 1 | 15 | 6.67 |
| ds_plus_09 | 1 | 15 | 6.67 |
| da_plus_13 | 1 | 16 | 6.25 |
| ds_bc_04 | 2 | 32 | 6.25 |
| ds_bc_02 | 1 | 16 | 6.25 |
| da_plus_09 | 1 | 26 | 3.85 |
# визуализируем результат
fig = px.bar(info_gr_tail, x=info_gr_tail.index, y='react_share', color=info_gr_tail.index,\
title='Доля сообщений с реакциями в канале инфо по когортам',\
color_discrete_sequence=px.colors.qualitative.Set2,\
text='react_share')
fig.update_layout(showlegend=False,
xaxis_title='Тип группы и когорта',
yaxis_title='Процент сообщений')
fig.show()
В отдельных когортах доля реакций не превышает 10%, что тоже требует дополнительного анализа.
Общией анализ сообщений в датасете показал, что больше всего сообщений генерируют группы da - их больше всего когорт. С разбивкой по каналам больше всего сообщений в канале projects, что объясняется самостоятельной работой над проектами и, как следствие, большим количеством вопросов.
Cамые длинные сообщения встречаются в канале library - в нем публикуются дополнительные тематическе материаламы: статьи, ссылки на учебники и полезные книги. Далее каналы projects и exerciser, где публикуются вопросы и объяснения к ним. Каналы info, teamwork и other имеют примерно одинаковое медианное количество символов в сообщениях.
Самые многословные группы - это студенты дата аналитик буткемп - короткосрочной программы анализа данных. Далее идут студенты расширенных групп (plus). Самые короткие сообщения пишут в канале мастерской.
Только 22,4% постов получают хотя бы одну реакцию, что довольно немного.
При анализе времени публикации сообщений выявлено, что самое большое количество сообщений выходит в 13:00 и в 15:00. В остальном в течение дня выход сообщений распределен более менее равномерно с 7:00 до 20:00, но к концу дня публикаций меньше. Больше всего сообщений генерируется в понедельник. Второй пик - в четверг, перед концом недели. Меньше всего в субботу. Основное количество сообщений опубликовано с 28 ноября 17 декабря 2022 - это примерно 3 недели. Возможно время активности обусловлено проведением спринта в большинстве когорт, представленных для анализа.
Исследование реакций показало, что больше всего реакций получают посты, выходящие в 7:00. Это время подъема или выхода на работу большинства людей. Второй пик - 13:00 - время обеда - но тут среднее количество реакций существенно ниже. Большее количество реакций получают посты, выходящие в понедельник - люди настроены на новую неделю после выходных и еще не загружены работой. Меньше всего реакций в воскресенье - выходной, большинству не до учебных чатов. Наибольшая активность наблюдается в середине и конце месяца, в первых числах выходило много постов (так как основной период анализа 3 недели - с 28 ноября по 17 декабря, предположительно недели спринта) и результат расчета среднего падает.
Исследование скорости реакции на сообщения исходя из ответов на созданные треды показало, что больше всего быстрых ответов получают сообщения, опубликованные в 13:00 и 15:00 - время начала и конца обеда у большинства людей. Также достаточно высокую активность имеют сообщения, вышедшие в 7:00, 10:00, 14:00, 19:00 - время прихода на работу, обеда и возвращения домой. Большее количество быстрых ответов получают посты, опубликованные в понедельник. Вторые по откликам - посты, опубликованные в четверг. Исследование дней месяца показало аномально высокую активность с середины месяца до 26 числа. Это связано с маленькой выборкой данных за эти даты, так как большинство сообщений было опубликовано с 28 ноября по 17 декабря. В эти даты пики это 28 ноября - судя по всему дата начала спринта (понедельник), 1 декабря, 12 декабря - то есть в конце месяца, в начале и в середине.
Исследование групп выявило, что самые часто реагируемые сообщения в группах masterskaya и data. Самые мало реагируемые сообщения в группах sql. В остальных группах процент реакций находится примерно на уровне 20%. Среднее количество реакций на пост равно 1 в группах data. В самых многочисленных по количеству сообщений группах da и ds среднее количество реакций на пост равно 0,65 и 0,42 соответственно. Как и в предущих расчетах меньше всего реакций в группах sql.
Исследование каналов показало самый высокий процент реакций в канале library - 64%. По всей видимости структурированная дополнительная информация воспринимается хорошо и имеет большой отклик. Сообщения в канале masterskaya также имеют достаточно высокий отклик - почти 46% сообщений имеют хотя бы 1 реакцию. Меньше всего реакций в канале projects - туда заходят задать вопрос или найти ответ, редко у кого возникает желание поставить реакцию. Сообщения в канале library имеют в среднем больше 4 реакций на пост - это очень хороший показатель. Сообщения в канале info - около 2 реакций на пост. Учитывая, что в этом канале чаще всего публикуются анонсы мероприятий, это довольно мало. Остальные каналы имеют меньше 1 реакции на пост. Можно предположить, что чем больше сообщений в группе, тем меньше процент отклика.
Исследование длины сообщений показало, что чем меньше сообщение по длине, тем более вероятно, что его дочитают до конца и что у него будут реакции. Но и коротких сообщений с нулевым реагированием достаточно.
Отдельно был проанализирован канал info. В этом канале представлено 2823 сообщения, медиана сообщения около 100 символов. Около 25% сообщений в этом канале имеют хотя бы 1 реакцию и среднее количество реакций на один пост около 2. Количество постов с реакциями около 40% - для анонсов не самый хороший отклик. Большинство постов имеет до 2 реакций. Самые активные группы - da_bc и ds - больше 4 реакций в среднем на один пост куратора. Самая малоактивная группа - sql - меньше 1 реакции на пост. Активность в некоторых когортах доходит до 80%, что обнадеживает - значит в других когортах можно достичь подобного результата, если подробнее проанализировать сообщения. К сожалению, а данном датасете для этого недостаточно данных. В отдельных когортах доля реакций не превышает 10%, что тоже требует дополнительного анализа.
Для анализа был предоставлен датасет с историей сообщений пользователей с 21.10.22 по 25.12.22 гг.
В результате предобработки мы удалили не несущий информацию столбец type. Выяснили, что большинство пропущенных значений client_id_msg - это сообщения со служебными метками, а также что сообщения от бота не имеют user id. Пропуски в столбцах subtype, client_msg_id, latest_reply, tread_ts, team, user оставили как есть. Удалили явные дубликаты, а также выяснили, что большинство сообщений находятся в пределах 55 слов и 400 символов.
Для проведения анализа в таблицу были добавлены следующие столбцы: channel_type - тип канала, group_type - тип группы, cohort - когорта, group_cohort - группа и когорта, count_of_reactions - количество реакций на пост, msg_hour - время сообщения, msg_weekday - день недели сообщения, msg_day - день месяца сообщения, msg_month - месяц сообщения, msg_date - дату сообщения. Также из таблицы были удалены служебные сообщения, не несущие информации для анализа.
Общий анализ сообщений в датасете показал, что больше всего сообщений генерируют группы da - их больше всего когорт. С разбивкой по каналам больше всего сообщений в канале projects, что объясняется самостоятельной работой над проектами и, как следствие, большим количеством вопросов.
Cамые длинные сообщения встречаются в канале library - в нем публикуются дополнительные тематическе материаламы: статьи, ссылки на учебники и полезные книги. Далее каналы projects и exerciser, где публикуются вопросы и объяснения к ним. Каналы info, teamwork и other имеют примерно одинаковое медианное количество символов в сообщениях.
Самые многословные группы - это студенты дата аналитик буткемп - короткосрочной программы анализа данных. Далее идут студенты расширенных групп (plus). Самые короткие сообщения пишут в канале мастерской.
Только 22,4% постов получают хотя бы одну реакцию, что довольно немного.
При анализе времени публикации сообщений выявлено, что самое большое количество сообщений выходит в 13:00 и в 15:00. В остальном в течение дня выход сообщений распределен более менее равномерно с 7:00 до 20:00, но к концу дня публикаций меньше. Больше всего сообщений генерируется в понедельник. Второй пик - в четверг, перед концом недели. Меньше всего в субботу. Основное количество сообщений опубликовано с 28 ноября 17 декабря 2022 - это примерно 3 недели. Возможно время активности обусловлено проведением спринта в большинстве когорт, представленных для анализа.
Исследование реакций показало, что больше всего реакций получают посты, выходящие в 7:00. Это время подъема или выхода на работу большинства людей. Второй пик - 13:00 - время обеда - но тут среднее количество реакций существенно ниже. Большее количество реакций получают посты, выходящие в понедельник - люди настроены на новую неделю после выходных и еще не загружены работой. Меньше всего реакций в воскресенье - выходной, большинству не до учебных чатов. Наибольшая активность наблюдается в середине и конце месяца, в первых числах выходило много постов (так как основной период анализа 3 недели - с 28 ноября по 17 декабря, предположительно недели спринта) и результат расчета среднего падает.
Исследование скорости реакции на сообщения исходя из ответов на созданные треды показало, что больше всего быстрых ответов получают сообщения, опубликованные в 13:00 и 15:00 - время начала и конца обеда у большинства людей. Также достаточно высокую активность имеют сообщения, вышедшие в 7:00, 10:00, 14:00, 19:00 - время прихода на работу, обеда и возвращения домой. Большее количество быстрых ответов получают посты, опубликованные в понедельник. Вторые по откликам - посты, опубликованные в четверг. Исследование дней месяца показало аномально высокую активность с середины месяца до 26 числа. Это связано с маленькой выборкой данных за эти даты, так как большинство сообщений было опубликовано с 28 ноября по 17 декабря. В эти даты пики это 28 ноября - судя по всему дата начала спринта (понедельник), 1 декабря, 12 декабря - то есть в конце месяца, в начале и в середине.
Исследование групп выявило, что самые часто реагируемые сообщения в группах masterskaya и data. Самые мало реагируемые сообщения в группах sql. В остальных группах процент реакций находится примерно на уровне 20%. Среднее количество реакций на пост равно 1 в группах data. В самых многочисленных по количеству сообщений группах da и ds среднее количество реакций на пост равно 0,65 и 0,42 соответственно. Как и в предущих расчетах меньше всего реакций в группах sql.
Исследование каналов показало самый высокий процент реакций в канале library - 64%. По всей видимости структурированная дополнительная информация воспринимается хорошо и имеет большой отклик. Сообщения в канале masterskaya также имеют достаточно высокий отклик - почти 46% сообщений имеют хотя бы 1 реакцию. Меньше всего реакций в канале projects - туда заходят задать вопрос или найти ответ, редко у кого возникает желание поставить реакцию. Сообщения в канале library имеют в среднем больше 4 реакций на пост - это очень хороший показатель. Сообщения в канале info - около 2 реакций на пост. Учитывая, что в этом канале чаще всего публикуются анонсы мероприятий, это довольно мало. Остальные каналы имеют меньше 1 реакции на пост. Можно предположить, что чем больше сообщений в группе, тем меньше процент отклика.
Исследование длины сообщений показало, что чем меньше сообщение по длине, тем более вероятно, что его дочитают до конца и что у него будут реакции. Но и коротких сообщений с нулевым реагированием достаточно.
Отдельно был проанализирован канал info. В этом канале представлено 2823 сообщения, медиана сообщения около 100 символов. Около 25% сообщений в этом канале имеют хотя бы 1 реакцию и среднее количество реакций на один пост около 2. Количество постов с реакциями около 40% - для анонсов не самый хороший отклик. Большинство постов имеет до 2 реакций. Самые активные группы - da_bc и ds - больше 4 реакций в среднем на один пост куратора. Самая малоактивная группа - sql - меньше 1 реакции на пост. Активность в некоторых когортах доходит до 80%, что обнадеживает - значит в других когортах можно достичь подобного результата, если подробнее проанализировать сообщения. К сожалению, а данном датасете для этого недостаточно данных. В отдельных когортах доля реакций не превышает 10%, что тоже требует дополнительного анализа.
da_bc_04, da_plus_18, da_bc_02, da_plus_17, а также в наименее активных когортах da_plus_09, ds_bc_02, для выявления возможных причин активности/неактивности.info, чтобы не отвлекать людей от работы или учебы.